﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using CommonFramework.Log;
using CommonFramework.Serialize.BytesArray.Private;

namespace CommonFramework.Serialize.BytesArray
{
    public class BytesWriter
    {
        private IBytesProvider _bytesProvider;
        public byte[] Bytes() => _bytesProvider.WriteBytesView();
        public int WriteIndex
        {
            get => _bytesProvider.WriteIndex;
            private set => _bytesProvider.WriteIndex = value;
        }

        public void AdvanceWriteIndex(int advanceCount)
        {
            WriteIndex = Math.Min(WriteIndex + advanceCount, _bytesProvider.TotalLength());
        }

        internal BytesWriter(IBytesProvider bytesProvider)
        {
            _bytesProvider = bytesProvider;
        }

        /// <summary>
        /// 剩余的可读字节长度
        /// </summary>
        public int DataLength() => _bytesProvider.DataLength();
        /// <summary>
        /// 剩余的可写字节长度
        /// </summary>
        public int RemainLength() => _bytesProvider.RemainLength();

        /// <summary>
        /// 已经读过的数据可以被舍弃,把用户数据移动到字节数组头部增加reamin
        /// </summary>
        public void TryMoveDataToOrigin(int threshold = 16)
        {
            if (_bytesProvider.DataLength() < threshold && _bytesProvider.ReadIndex >= 3 * threshold)
                _bytesProvider.MoveDataToOrigin();
        }

        /// <summary>
        /// 如果写入空间不足则扩容
        /// </summary>
        public void TryExtendWithAddSize(int addSize)
        {
            if (RemainLength() < addSize)
            {
                TryMoveDataToOrigin();

                if (RemainLength() < addSize)
                {
                    int newSize = DataLength() + addSize;
                    _bytesProvider.ExtendSizeTo(newSize);
                }

                if (RemainLength() < addSize)
                    LogUtil.LogNotReachHereError();
            }
        }
        /// <summary>
        /// 从字节数组写入数据,返回成功写入的字节数
        /// </summary>
        public int WriteBytes(byte[] target, int offset, int count)
        {
            int writeLen = Math.Min(target.Length - offset, count);
            if (writeLen > 0)
            {
                TryExtendWithAddSize(writeLen);

                int copiedLen = 0;
                while (copiedLen < writeLen)
                {
                    int batchLen = Math.Min(Bytes().Length - WriteIndex, writeLen);
                    Buffer.BlockCopy(target, offset+ copiedLen, Bytes(), WriteIndex, batchLen);

                    copiedLen += batchLen;
                    WriteIndex += batchLen;
                }
            }
            return writeLen;
        }
        public int WriteBytes(byte[] target, int offset) => WriteBytes(target, offset, target.Length);
        public int WriteBytes(byte[] target) => WriteBytes(target, 0, target.Length);

        /// <summary>
        /// 从流中写入数据
        /// </summary>
        public int ReadStream(Stream stream, int offset,int count)
        {
            if (!stream.CanRead)
                return 0;
            int writeLen = (int)Math.Min(count, stream.Length-offset);
            if (writeLen > 0)
            {
                TryExtendWithAddSize(writeLen);

                int copiedLen = 0;
                while (copiedLen < writeLen)
                {
                    int batchLen = Math.Min(Bytes().Length - WriteIndex, writeLen);
                    int realbatchLen= stream.Read(Bytes(), WriteIndex, batchLen);

                    copiedLen += realbatchLen;
                    WriteIndex += realbatchLen;
                }
            }
            WriteIndex += writeLen;
            return writeLen;
        }

        #region 写入基础类型
        public void WriteByte(byte val)
        {
            int size = sizeof(byte);
            TryExtendWithAddSize(size);
            Bytes()[WriteIndex] = val;
            WriteIndex += size;
        }
        public void WriteSByte(sbyte val)
        {
            int size = sizeof(sbyte);
            TryExtendWithAddSize(size);
            Bytes()[WriteIndex] = (byte)val;
            WriteIndex += size;
        }
        public void WriteBool(bool val)
        {
            int size = sizeof(bool);
            TryExtendWithAddSize(size);
            Bytes()[WriteIndex] = (byte)(val ? 1 : 0);
            WriteIndex += size;
        }
        public void WriteChar(char val)
        {
            int size = sizeof(char);
            TryExtendWithAddSize(size);
            BytesConvertUtil.GetBytes(Bytes(),WriteIndex, val);
            WriteIndex += size;
        }
        public void WriteInt16(short val)
        {
            int size = sizeof(short);
            TryExtendWithAddSize(size);
            BytesConvertUtil.GetBytes(Bytes(), WriteIndex, val);
            WriteIndex += size;
        }
        public void WriteUInt16(ushort val)
        {
            int size = sizeof(ushort);
            TryExtendWithAddSize(size);
            BytesConvertUtil.GetBytes(Bytes(), WriteIndex, val);
            WriteIndex += size;
        }
        public void WriteInt32(int val)
        {
            int size = sizeof(int);
            TryExtendWithAddSize(size);
            BytesConvertUtil.GetBytes(Bytes(), WriteIndex, val);
            WriteIndex += size;
        }
        public void WriteUInt32(uint val)
        {
            int size = sizeof(uint);
            TryExtendWithAddSize(size);
            BytesConvertUtil.GetBytes(Bytes(), WriteIndex, val);
            WriteIndex += size;
        }
        public void WriteInt64(long val)
        {
            int size = sizeof(long);
            TryExtendWithAddSize(size);
            BytesConvertUtil.GetBytes(Bytes(), WriteIndex, val);
            WriteIndex += size;
        }
        public void WriteUInt64(ulong val)
        {
            int size = sizeof(ulong);
            TryExtendWithAddSize(size);
            BytesConvertUtil.GetBytes(Bytes(), WriteIndex,val);
            WriteIndex += size;
        }
        public void WriteFloat(float val)
        {
            int size = sizeof(float);
            TryExtendWithAddSize(size);
            BytesConvertUtil.GetBytes(Bytes(), WriteIndex, val);
            WriteIndex += size;
        }
        public void WriteDouble(double val)
        {
            int size = sizeof(double);
            TryExtendWithAddSize(size);
            BytesConvertUtil.GetBytes(Bytes(), WriteIndex, val);
            WriteIndex += size;
        }
        public void WriteString(string val)
        {
            if (string.IsNullOrEmpty(val))
            {
                WriteInt32(0);
                return;
            }

            //put bytes count
            int bytesCount = Encoding.UTF8.GetByteCount(val);
            TryExtendWithAddSize(bytesCount + sizeof(int));
            WriteInt32(bytesCount);

            //put string
            int PER_BATCH_CHAR_SIZE = 10240;
            if (val.Length <= PER_BATCH_CHAR_SIZE)
            {
                Encoding.UTF8.GetBytes(val, 0, val.Length, Bytes(), WriteIndex);
                WriteIndex += bytesCount;
            }
            else
            {
                int totalCharCount = val.Length;
                int writedCharCount = 0;
                while (writedCharCount < totalCharCount)
                {
                    int batchCount = Math.Min(totalCharCount - writedCharCount, PER_BATCH_CHAR_SIZE);
                    string writeStr = val.Substring(writedCharCount, batchCount);
                    int writedBytesCount= Encoding.UTF8.GetBytes(writeStr, 0, batchCount, Bytes(), WriteIndex);

                    writedCharCount += batchCount;
                    WriteIndex += writedBytesCount;
                }
            }
        }
        #endregion

        public void WriteSerializable<T>(T serializable) where T : IBytesSerializable
        {
            serializable.WriteToBytes(this);
        }

        #region 写元素泛型

        public void WritePrimitive(object val)
        {
            Type primitiveType = val.GetType();
            if (primitiveType.IsPrimitive == false)
                return;

            var typeCode = Type.GetTypeCode(primitiveType);
            switch (typeCode)
            {
                case TypeCode.Boolean: 
                    WriteBool((bool) val);
                    break;
                case TypeCode.Char:
                    WriteChar((char)val);
                    break;
                case TypeCode.SByte:
                    WriteSByte((sbyte)val);
                    break;
                case TypeCode.Byte:
                    WriteByte((byte)val);
                    break;
                case TypeCode.Int16:
                    WriteInt16((short)val);
                    break;
                case TypeCode.UInt16:
                    WriteUInt16((ushort)val);
                    break;
                case TypeCode.Int32:
                    WriteInt32((int)val);
                    break;
                case TypeCode.UInt32:
                    WriteUInt32((uint) val);
                    break;
                case TypeCode.Int64:
                    WriteInt64((long)val);
                    break;
                case TypeCode.UInt64:
                    WriteUInt64((ulong)val);
                    break;
                case TypeCode.Single:
                    WriteFloat((float)val);
                    break;
                case TypeCode.Double:
                    WriteDouble((double)val);
                    break;
                case TypeCode.String:
                    WriteString((string)val);
                    break;
                default:
                    LogUtil.LogWarning("unsupport type for BytesWriter,with type=" + primitiveType);
                    break;
            }
        }
        public void Write(object val)
        {
            //null flag
            WriteBool(val==null);
            if (val == null)
                return;

            Type type = val.GetType();
            if (type.IsPrimitive)
            {
                WritePrimitive(val);
            }
            else if (typeof(IBytesSerializable).IsAssignableFrom(type))
            {
                WriteSerializable(val as IBytesSerializable);
            }
            else if (type == typeof(string))
            {
                WriteString((string) val);
            }
            else if (type.IsArray)
            {
                WriteArray(val as Array);
            }
            else if (type.IsGenericType)
            {
                Type genericType = type.GetGenericTypeDefinition();
                if (genericType == typeof(List<>)) //List容器类型
                {
                    WriteList(val as IList);
                }
                else if (genericType == typeof(Dictionary<,>)) //映射类型
                {
                    WriteDictionary(val as IDictionary);
                }
                else
                {
                    LogUtil.LogNotReachHereError("unsupport generic type");
                }
            }
            else
            {
                LogUtil.LogError($"unsupport write for type={type.FullName} ,val={val}");
            }
        }
        #endregion

        #region 写容器泛型
        public void WriteArray(Array arr)
        {
            if (arr == null || arr.Length == 0)
            {
                WriteInt16(0);
                return;
            }

            WriteInt16((short) arr.Length);
            foreach (var elem in arr)
            {
                Write(elem);
            }
        }
        public void WriteArray<T>(T[] arr)
        {
            if (arr == null || arr.Length == 0)
            {
                WriteInt16(0);
                return;
            }
            short arraySize = (short)arr.Length;
            WriteInt16(arraySize);
            for (int i = 0; i < arraySize; ++i)
            {
                Write(arr[i]);
            }
        }
        public void WriteList(IList list)
        {
            if (list == null || list.Count == 0)
            {
                WriteInt16(0);
                return;
            }
            short size = (short)list.Count;
            WriteInt16(size);
            for (int i = 0; i < size; ++i)
            {
                Write(list[i]);
            }
        }
        public void WriteList<T>(List<T> list)
        {
            if (list == null || list.Count == 0)
            {
                WriteInt16(0);
                return;
            }

            short size = (short) list.Count;
            WriteInt16(size);
            for (int i = 0; i < size; ++i)
            {
                Write(list[i]);
            }
        }
        public void WriteDictionary(IDictionary dict)
        {
            if (dict == null || dict.Count == 0)
            {
                WriteInt16(0);
                return;
            }

            WriteInt16((short) dict.Count);
            foreach (DictionaryEntry kvp in dict)
            {
                Write(kvp.Key);
                Write(kvp.Value);
            }
        }
        public void WriteDictionary<Key_T,Val_T>(Dictionary<Key_T, Val_T> dict)
        {
            if (dict == null || dict.Count == 0)
            {
                WriteInt16(0);
                return;
            }

            WriteInt16((short)dict.Count);
            foreach (var kvp in dict)
            {
                Write(kvp.Key);
                Write(kvp.Value);
            }
        }
        #endregion
    }
}